/*!
 * \file WtBtDtReaderAD.cpp
 * \project WonderTrader
 *
 * \author Wesley
 * \date 2020/03/30
 * 
 * \brief WonderTrader高级回测数据读取器实现文件
 * 
 * \details 该文件实现了WtBtDtReaderAD类的所有功能，提供基于LMDB的高性能回测数据读取：
 *          - LMDB数据库集成：使用Lightning Memory-Mapped Database进行零拷贝数据读取
 *          - 多周期K线数据读取：支持1分钟、5分钟、日线数据的批量读取
 *          - Tick数据读取：支持按日期范围读取高频Tick数据
 *          - 智能数据库管理：按需打开和缓存数据库连接
 *          - 高效查询机制：使用LMDB范围查询实现快速数据检索
 *          
 *          数据库组织结构：
 *          - K线数据：{basedir}/{period}/{exchg}/ (如 ./min1/CFFEX/)
 *          - Tick数据：{basedir}/ticks/{exchg}/{code}/ (如 ./ticks/CFFEX/IF2101/)
 *          
 *          性能优化：
 *          - 数据库连接池：避免重复打开数据库
 *          - 内存映射访问：实现零拷贝数据读取
 *          - 批量查询：使用范围查询减少查询次数
 */

#include "WtBtDtReaderAD.h"
#include "LMDBKeys.h"

#include "../Includes/WTSStruct.h"
#include "../Includes/WTSVariant.hpp"
#include "../Share/StrUtil.hpp"
#include "../Share/StdUtils.hpp"

USING_NS_WTP;

//By Wesley @ 2022.01.05
#include "../Share/fmtlib.h"

/*!
 * \brief 线程安全的回测读取器日志输出模板函数
 * \tparam Args 可变参数类型
 * \param sink 回测数据读取器回调接口
 * \param ll 日志级别
 * \param format 格式化字符串
 * \param args 格式化参数
 * 
 * 使用fmt库进行格式化，通过线程局部存储确保线程安全。
 * 该函数为WtBtDtReaderAD提供统一的日志输出机制。
 */
template<typename... Args>
inline void pipe_btreader_log(IBtDtReaderSink* sink, WTSLogLevel ll, const char* format, const Args&... args)
{
	if (sink == NULL)
		return;

	static thread_local char buffer[512] = { 0 };	// 线程局部缓冲区，避免线程竞争
	memset(buffer, 0, 512);
	fmt::format_to(buffer, format, args...);		// 使用fmt库进行高效格式化

	sink->reader_log(ll, buffer);
}

/*!
 * \brief C接口导出函数
 * 
 * 提供C风格的接口用于动态库加载和跨语言调用
 */
extern "C"
{
	/*!
	 * \brief 创建WtBtDtReaderAD实例的工厂函数
	 * \return IBtDtReader接口指针
	 * 
	 * 用于动态库加载时创建高级回测数据读取器实例
	 */
	EXPORT_FLAG IBtDtReader* createBtDtReader()
	{
		IBtDtReader* ret = new WtBtDtReaderAD();
		return ret;
	}

	/*!
	 * \brief 销毁WtBtDtReaderAD实例的清理函数
	 * \param reader 回测数据读取器指针
	 * 
	 * 安全地销毁回测数据读取器实例
	 */
	EXPORT_FLAG void deleteBtDtReader(IBtDtReader* reader)
	{
		if (reader != NULL)
			delete reader;
	}
};

/*!
 * \brief WtBtDtReaderAD构造函数
 * 
 * 初始化回测数据读取器，设置默认状态
 */
WtBtDtReaderAD::WtBtDtReaderAD()
{
}

/*!
 * \brief WtBtDtReaderAD析构函数
 * 
 * 清理资源，关闭所有数据库连接
 */
WtBtDtReaderAD::~WtBtDtReaderAD()
{

}

/*!
 * \brief 初始化回测数据读取器
 * \param cfg 配置参数变体对象
 * \param sink 回测数据读取器回调接口
 * 
 * \details 完成以下初始化工作：
 *          1. 保存回调接口引用
 *          2. 读取数据库根目录配置
 *          3. 标准化路径格式
 *          4. 记录初始化信息日志
 */
void WtBtDtReaderAD::init(WTSVariant* cfg, IBtDtReaderSink* sink)
{
	_sink = sink;

	if (cfg == NULL)
		return;

	_base_dir = cfg->getCString("path");			// 获取数据库根目录
	_base_dir = StrUtil::standardisePath(_base_dir);	// 标准化路径格式

	pipe_btreader_log(_sink, LL_INFO, "WtBtDtReaderAD initialized, root data dir is {}", _base_dir);
}

/*!
 * \brief 读取原始K线数据
 * \param exchg 交易所代码
 * \param code 合约代码
 * \param period K线周期
 * \param buffer 输出缓冲区
 * \return 读取是否成功
 * 
 * \details 从LMDB数据库中批量读取K线数据：
 *          1. 获取对应周期的数据库实例
 *          2. 构造查询键范围（指定合约的所有K线）
 *          3. 执行范围查询获取所有匹配数据
 *          4. 将数据批量复制到输出缓冲区
 */
bool WtBtDtReaderAD::read_raw_bars(const char* exchg, const char* code, WTSKlinePeriod period, std::string& buffer)
{
	// 直接从LMDB读取
	WtLMDBPtr db = get_k_db(exchg, period);
	if (db == NULL)
		return false;

	pipe_btreader_log(_sink, LL_DEBUG, "Reading back {} bars of {}.{}...", PERIOD_NAME[period], exchg, code);
	
	// 构造查询范围：从时间戳0到最大时间戳
	WtLMDBQuery query(*db);
	LMDBBarKey rKey(exchg, code, 0xffffffff);	// 右边界（最大时间戳）
	LMDBBarKey lKey(exchg, code, 0);			// 左边界（最小时间戳）
	
	// 执行范围查询
	int cnt = query.get_range(std::string((const char*)&lKey, sizeof(lKey)), std::string((const char*)&rKey, sizeof(rKey)),
		[this, &buffer, &lKey](const ValueArray& ayKeys, const ValueArray& ayVals) {
		if (ayVals.empty())
			return;

		// 批量复制数据到输出缓冲区
		std::size_t cnt = ayVals.size();
		auto szUnit = sizeof(WTSBarStruct);
		buffer.resize(szUnit*cnt);
		char* cursor = (char*)buffer.data();
		for(const std::string& item : ayVals)
		{
			memcpy(cursor, item.data(), szUnit);
			cursor += szUnit;
		}
	});

	return true;
}

/*!
 * \brief 读取原始Tick数据
 * \param exchg 交易所代码
 * \param code 合约代码
 * \param uDate 日期（YYYYMMDD格式）
 * \param buffer 输出缓冲区
 * \return 读取是否成功
 * 
 * \details 从LMDB数据库中读取指定日期的Tick数据：
 *          1. 获取对应合约的Tick数据库实例
 *          2. 构造查询键范围（指定日期的所有Tick）
 *          3. 执行范围查询获取当日所有Tick数据
 *          4. 将数据批量复制到输出缓冲区
 */
bool WtBtDtReaderAD::read_raw_ticks(const char* exchg, const char* code, uint32_t uDate, std::string& buffer)
{
	// 直接从LMDB读取
	WtLMDBPtr db = get_t_db(exchg, code);
	if (db == NULL)
		return false;

	pipe_btreader_log(_sink, LL_DEBUG, "Reading back ticks on {} of {}.{}...", uDate, exchg, code);
	
	// 构造查询范围：指定日期的全天时间范围
	WtLMDBQuery query(*db);
	LMDBHftKey rKey(exchg, code, uDate, 240000000);	// 右边界（当日24点）
	LMDBHftKey lKey(exchg, code, uDate, 0);			// 左边界（当日0点）
	
	// 执行范围查询
	int cnt = query.get_range(std::string((const char*)&lKey, sizeof(lKey)), std::string((const char*)&rKey, sizeof(rKey)),
		[this, &buffer, &lKey](const ValueArray& ayKeys, const ValueArray& ayVals) {
		if (ayVals.empty())
			return;

		// 批量复制数据到输出缓冲区
		std::size_t cnt = ayVals.size();
		auto szUnit = sizeof(WTSTickStruct);
		buffer.resize(szUnit*cnt);
		char* cursor = (char*)buffer.data();
		for (const std::string& item : ayVals)
		{
			memcpy(cursor, item.data(), szUnit);
			cursor += szUnit;
		}
	});

	return true;
}

/*!
 * \brief 获取K线数据库实例
 * \param exchg 交易所代码
 * \param period K线周期
 * \return LMDB数据库智能指针
 * 
 * \details 根据K线周期获取对应的数据库实例：
 *          1. 根据周期选择对应的数据库映射表和子目录
 *          2. 检查数据库是否已经打开并缓存
 *          3. 如果未缓存，则创建新的数据库实例
 *          4. 验证数据库目录是否存在
 *          5. 打开数据库并加入缓存
 * 
 * \note 使用连接池机制，避免重复打开同一数据库
 */
WtBtDtReaderAD::WtLMDBPtr WtBtDtReaderAD::get_k_db(const char* exchg, WTSKlinePeriod period)
{
	WtLMDBMap* the_map = NULL;
	std::string subdir;
	
	// 根据K线周期选择对应的数据库映射表和子目录
	if (period == KP_Minute1)
	{
		the_map = &_exchg_m1_dbs;
		subdir = "min1";
	}
	else if (period == KP_Minute5)
	{
		the_map = &_exchg_m5_dbs;
		subdir = "min5";
	}
	else if (period == KP_DAY)
	{
		the_map = &_exchg_d1_dbs;
		subdir = "day";
	}
	else
		return std::move(WtLMDBPtr());	// 不支持的周期类型

	// 检查数据库是否已经打开并缓存
	auto it = the_map->find(exchg);
	if (it != the_map->end())
		return std::move(it->second);

	// 创建新的数据库实例
	WtLMDBPtr dbPtr(new WtLMDB(true));
	std::string path = StrUtil::printf("%s%s/%s/", _base_dir.c_str(), subdir.c_str(), exchg);
	
	// 验证数据库目录是否存在
	if (!StdFile::exists(path.c_str()))
		return std::move(WtLMDBPtr());

	// 尝试打开数据库
	if (!dbPtr->open(path.c_str()))
	{
		pipe_btreader_log(_sink, LL_ERROR, "Opening {} db if {} failed: {}", subdir, exchg, dbPtr->errmsg());
		return std::move(WtLMDBPtr());
	}
	else
	{
		pipe_btreader_log(_sink, LL_DEBUG, "{} db of {} opened", subdir, exchg);
	}

	// 将数据库实例加入缓存
	(*the_map)[exchg] = dbPtr;
	return std::move(dbPtr);
}

/*!
 * \brief 获取Tick数据库实例
 * \param exchg 交易所代码
 * \param code 合约代码
 * \return LMDB数据库智能指针
 * 
 * \details 根据交易所和合约代码获取对应的Tick数据库实例：
 *          1. 构造数据库键（"exchg.code"格式）
 *          2. 检查数据库是否已经打开并缓存
 *          3. 如果未缓存，则创建新的数据库实例
 *          4. 验证数据库目录是否存在
 *          5. 打开数据库并加入缓存
 * 
 * \note 使用连接池机制，避免重复打开同一数据库
 * \warning 这里有一个bug：应该使用key而不是exchg作为缓存键
 */
WtBtDtReaderAD::WtLMDBPtr WtBtDtReaderAD::get_t_db(const char* exchg, const char* code)
{
	// 构造数据库键："exchg.code"格式
	std::string key = StrUtil::printf("%s.%s", exchg, code);
	
	// 检查数据库是否已经打开并缓存
	auto it = _tick_dbs.find(key);
	if (it != _tick_dbs.end())
		return std::move(it->second);

	// 创建新的数据库实例
	WtLMDBPtr dbPtr(new WtLMDB(true));
	std::string path = StrUtil::printf("%sticks/%s/%s", _base_dir.c_str(), exchg, code);
	
	// 验证数据库目录是否存在
	if (!StdFile::exists(path.c_str()))
		return std::move(WtLMDBPtr());

	// 尝试打开数据库
	if (!dbPtr->open(path.c_str()))
	{
		pipe_btreader_log(_sink, LL_ERROR, "Opening tick db of {}.{} failed: {}", exchg, code, dbPtr->errmsg());
		return std::move(WtLMDBPtr());
	}
	else
	{
		pipe_btreader_log(_sink, LL_DEBUG, "Tick db of {}.{} opened", exchg, code);
	}

	// 将数据库实例加入缓存 - 注意：这里使用key而不是exchg
	_tick_dbs[key] = dbPtr;	// 修复：应该使用key作为缓存键
	return std::move(dbPtr);
}